﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.ServiceModel.Syndication;
using System.Xml;
using DotNetBurner.Core;
using DotNetBurner.Model;
using DotNetBurner.Web;
using HtmlAgilityPack;

namespace DotNetBurner.SubmitBot
{
    public class FeedProcessor
    {
        string[] _feeds;
        Repository _repo;
        IEnumerable<Tag> _topTags;
        IEnumerable<Category> _categories;
        string[] _topTagsNames;
        string _cachePath;
        string _userName;
        ILog _logger;
        bool _autoPublish;
        bool _sendTrackBack;
        string _pageGlimpseKey;
        string _siteUrl;
        string _siteName;
        string _trackbackMessage;
        int _userId;

        public FeedProcessor(string[] feeds, string cachePath, string connStr, string userName, 
                             string siteUrl, string pageGlimpseKey, string siteName,
                             string trackbackMessage, bool sendTrackBack, bool autoPublish, 
                             ILog logger)
        {
            _logger = logger;
            _feeds = feeds;
            _cachePath = cachePath;
            _repo = new Repository(connStr);
            _categories = _repo.GetCategories();
            _topTags = _repo.GetTags(250);
            _autoPublish = autoPublish;
            _sendTrackBack = sendTrackBack;
            _userName = userName;
            _siteUrl = siteUrl;
            _pageGlimpseKey = pageGlimpseKey;
            _siteName = siteName;
            _trackbackMessage = trackbackMessage;
            _userId = _repo.GetUserIdByName(_userName);
            _topTagsNames = (from t in _topTags select String.Format(" {0} ", t.Name)).ToArray();
        }

        public void Process()
        {
            foreach (var feed in _feeds)
            {
                DateTime dtime = DateTime.MinValue;

                FileInfo info = new FileInfo(GetFeedCachePath(feed));

                if (info.Exists)
                    dtime = info.LastWriteTimeUtc;

                //TODO: lastdownloaded param
                WebUtility.BeginGetDataFromUrl(EndGetFeed, feed, feed, 20 * 1000, true, dtime);
            }
        }

        void EndGetFeed(IAsyncResult result)
        {
            try
            {
                HttpRequestState reqState = (HttpRequestState)result.AsyncState;

                string feedUrl = (string)reqState.State;
                string path = GetFeedCachePath(feedUrl);

                //check if the feed changed (WebUtility doesnt download if it didn't change)
                if (!reqState.ChangedSinceLastDownload)
                    return;

                string dataContent = WebUtility.EndGetDataFromUrl(result);
                File.WriteAllText(path, dataContent.Trim(), reqState.Encoding);

                XmlReader reader = XmlReader.Create(path);
                SyndicationFeed syndicationFeed = SyndicationFeed.Load(reader);

                foreach (SyndicationItem syndicationItem in syndicationFeed.Items)
                {
                    if (syndicationItem.PublishDate.UtcDateTime < DateTime.Now.AddYears(-3).ToUniversalTime())
                        continue;

                    //post link
                    string url = String.Empty;

                    if (syndicationItem.Links.Count > 0)
                    {
                        if (syndicationItem.Links.Count == 1)
                            url = syndicationItem.Links[0].Uri.OriginalString;
                        else
                        {
                            foreach (SyndicationLink link in syndicationItem.Links)
                                if (link.RelationshipType.Equals("alternate",
                                                                 StringComparison.OrdinalIgnoreCase))
                                {
                                    url = link.Uri.OriginalString;
                                    break;
                                }
                        }
                    }

                    string proxyFreeUrl = url;

                    if (syndicationItem.ElementExtensions != null)
                    {
                        //find the original link
                        XmlReader xmlReader = syndicationItem.ElementExtensions.GetReaderAtElementExtensions();

                        while (xmlReader.Read())
                        {
                            //aha! got you feedburner!
                            if (xmlReader.Name == "feedburner:origLink")
                            {
                                proxyFreeUrl = xmlReader.ReadElementString();
                                break;
                            }
                        }
                    }

                    if (proxyFreeUrl.Length == 0)
                    {
                        _logger.Message("Url not found!");
                        continue;
                    }

                    _logger.Message(proxyFreeUrl);

                    lock (_repo)
                    {
                        if (_repo.GetStoryByUrl(proxyFreeUrl) != -1)
                            continue;
                    }

                    string content;

                    if (syndicationItem.Summary != null)
                        content = syndicationItem.Summary.Text;
                    else if (syndicationItem.Content is TextSyndicationContent)
                        content = ((TextSyndicationContent)syndicationItem.Content).Text;
                    else
                        continue;

                    string title = HtmlEntity.DeEntitize(syndicationItem.Title.Text).Trim();

                    if (title.Length < 15)
                    {
                        _logger.Message("Title too short!");
                        continue;
                    }

                    //only text
                    content = HtmlUtil.ConvertToText(content);
                    string summary = GetSummary(content).Replace("(read more)", "").RemoveNewLines().Trim();

                    //replace : with "..."
                    if (summary.EndsWith(":"))
                    {
                        summary.Remove(summary.Length - 1, 1);
                        summary += "...";
                    }

                    if (summary.Length < 30)
                    {
                        _logger.Message("Summary too short!");
                        continue;
                    }

                    int categoryId = -1;

                    categoryId = GetCategory(title);

                    //not found, try on content
                    if (categoryId == -1)
                        categoryId = GetCategory(content);

                    if (categoryId == -1)
                    {
                        foreach (var cat in syndicationItem.Categories)
                        {
                            Category category = null;

                            lock (_repo)
                            {
                                category = _repo.GetCategoryByName(cat.Name);
                            }

                            if (category != null && categoryId != 0 && category.ParentId.HasValue)
                                categoryId = category.CategoryId;
                        }
                    }

                    if (categoryId == -1)
                    {
                        _logger.Message("Category not found!");
                        continue;
                    }

                    List<string> tags = new List<string>();

                    //search for tags - first try on summary
                    tags.AddRange(GetTags(summary));

                    //not found on summary, try on title
                    if (tags.Count == 0)
                        tags.AddRange(GetTags(title));

                    //not found on title, try on content
                    if (tags.Count == 0)
                        tags.AddRange(GetTags(content));

                    string categoryName;

                    lock (_repo)
                    {
                        categoryName = _repo.GetCategoryById(categoryId).Name;
                    }

                    //add the category name as an tag
                    if (categoryName.Length <= 30 && categoryName.Length >= 2
                        && tags.Count(f => f.Equals(categoryName, StringComparison.InvariantCultureIgnoreCase)) == 0)
                    {
                        tags.Add(categoryName);
                    }

                    string strTags = String.Join(", ", tags.ToArray());

                    DateTime publishDate = syndicationItem.PublishDate.DateTime.Date.ToUniversalTime();


                    if (publishDate > DateTime.UtcNow)
                        publishDate = DateTime.UtcNow;

                    int storyId = -1;

                    lock (_repo)
                    {
                        storyId = _repo.SubmitStory(proxyFreeUrl, title, categoryId, summary, strTags, _userId, publishDate);

                        if (_autoPublish && storyId != -1)
                            _repo.StoryPublish(storyId, publishDate);
                    }

                    if (storyId != -1)
                    {

                        string storyUrl = String.Format("{0}/detail/{1}/{2}", _siteUrl, storyId, title.ConvertToUrlPath());

                        try
                        {
                            //TODO: configuration options
                            if (!String.IsNullOrEmpty(_pageGlimpseKey))
                                PageGlimpse.RequestSiteThumbnail(proxyFreeUrl, _pageGlimpseKey);

                            if (_sendTrackBack && !String.IsNullOrEmpty(_trackbackMessage) && !String.IsNullOrEmpty(_siteName))
                            {
                                Trackback.Send(proxyFreeUrl, title, storyUrl, _trackbackMessage, _siteName, categoryName);
                            }

                        }
                        catch (Exception exception)
                        {
                            _logger.Message(exception.Message);
                        }
                    }
                }
            }
            catch (Exception exception)
            {
                _logger.Message(exception.Message);
            }
        }

        public string[] GetTags(string content)
        {
            return (from t in
                       content.FindKeyWords(_topTagsNames, 5)
                    select t.Trim()).ToArray();
        }

        public int GetCategory(string content)
        {
            string cat = GetCategory(content, _categories);

            if (cat.Length != 0)
            {
                Category objCategory = _repo.GetCategoryByName(cat);

                if (objCategory != null)
                    return objCategory.CategoryId;
            }

            return -1;
        }

        public static string GetCategory(string content, IEnumerable<Category> categories)
        {
            //add categories
            List<Category> categs = new List<Category>();

            foreach (Category cat in categories)
            {
                categs.AddRange(cat.SubCategories);
            }

            string[] strCategories = (from c in categs
                                      select c.Name).ToArray();

            strCategories = content.FindKeyWords(strCategories, 1);

            if (strCategories.Length > 0)
                return strCategories[0];

            return String.Empty;
        }

        static string GetSummary(string description)
        {
            return description.GetSummary(250, 500);
        }

        public string GetFeedCachePath(string feedUrl)
        {
            return Path.Combine(_cachePath, String.Concat(feedUrl.ConvertToFileName(), ".xml"));
        }
    }
}
